use std::{str::FromStr, sync::Arc};

use futures::TryFutureExt;
use jsonrpsee::types::ErrorObjectOwned;
use tokio::sync::{oneshot, Mutex, RwLock};
use tracing::*;

use aspect_client_rpc::*;
use aspect_ident::DirPackage;
use aspect_rpc_common::*;

use aspect_client::{credentials::HomeserverCreds, device_resolver, peer_manager, signer_manager};

pub struct RpcImpl {
    // todo
    peer_man: Arc<peer_manager::PeerManager>,
    device_resolver: Arc<device_resolver::DeviceResolver>,
    signer_man: Arc<RwLock<signer_manager::SignerManager>>,

    stop_sig: Mutex<Option<oneshot::Sender<()>>>,
}

impl RpcImpl {
    pub fn new(
        peer_man: Arc<peer_manager::PeerManager>,
        device_resolver: Arc<device_resolver::DeviceResolver>,
        signer_man: Arc<RwLock<signer_manager::SignerManager>>,
        stop_sig: oneshot::Sender<()>,
    ) -> Self {
        Self {
            peer_man,
            device_resolver,
            signer_man,
            stop_sig: Mutex::new(Some(stop_sig)),
        }
    }

    pub async fn get_current_dirpkg(&self) -> DirPackage {
        let sm = self.signer_man.read().await;
        sm.current_dirpkg().clone()
    }
}

#[async_trait]
impl ClientRpcServer for RpcImpl {
    async fn status(&self) -> RpcResult<StatusReport> {
        let dirpkg = self.get_current_dirpkg().await;
        let creds = self.peer_man.get_creds().await;
        let username = creds
            .map(|c| c.username)
            .unwrap_or_else(|| "[unset]".to_owned());
        Ok(StatusReport {
            device_id: dirpkg.last_update().ident().to_string(),
            self_ref: UserRef(username),
            status: Status::Idle,
            peers: Vec::new(),
        })
    }

    async fn stop(&self) -> RpcResult<()> {
        let mut sig = self.stop_sig.lock().await;
        if let Some(sig) = sig.take() {
            sig.send(()).expect("rpc/stop: send stop sig");
        }

        Ok(())
    }

    async fn add_peer(&self, peer_str: String) -> RpcResult<()> {
        // TODO
        info!(%peer_str, "ignoring add_peer call");
        Ok(())
    }

    async fn register(
        &self,
        homeserver: String,
        username: String,
        password: String,
        extra_cred: Option<Vec<u8>>,
    ) -> RpcResult<()> {
        // TODO
        info!(%homeserver, %username, "registering at homeserver");
        if extra_cred.is_some() {
            warn!("ignoring non-null extra_cred");
        }

        let span = debug_span!("rpc-register", %username);

        // Register the user and the device.
        self.peer_man
            .connect_and_register(homeserver, username, password)
            .instrument(span)
            .map_err(conv_peer_man_err)
            .await?;

        Ok(())
    }

    async fn login(&self, homeserver: String, username: String, password: String) -> RpcResult<()> {
        info!(%homeserver, %username, "logging into homeserver");

        // Log into the server and possibly register the device.
        self.peer_man
            .connect_and_login(homeserver, username, password)
            .map_err(conv_peer_man_err)
            .await?;

        Ok(())
    }

    async fn get_device_data(&self, devident: String) -> RpcResult<HexBuf> {
        let devident_parsed = DeviceIdent::from_str(&devident).map_err(conv_device_err)?;

        let dirpkg: DirPackage = self
            .device_resolver
            .lookup_device(&devident_parsed)
            .map_err(conv_client_err)
            .await?;

        Ok(HexBuf::try_from_codec(&dirpkg).map_err(conv_codec_err)?)
    }

    async fn get_hs_user_devices(&self, user_query: String) -> RpcResult<Vec<String>> {
        let idents = self
            .device_resolver
            .lookup_user_devices(&user_query)
            .map_err(conv_client_err)
            .await?;

        let ident_strs = idents.into_iter().map(|i| i.to_string()).collect();
        Ok(ident_strs)
    }

    async fn create_channel(&self, init_users: Vec<UserRef>) -> RpcResult<ChannelRef> {
        // TODO
        info!("creating channel");
        Ok(ChannelRef(String::new()))
    }

    async fn send_channel_msg(&self, ch: ChannelRef, msg_payload: String) -> RpcResult<()> {
        // TODO
        info!(?ch, "sending message to channel");
        Ok(())
    }

    async fn list_device_queues(&self) -> RpcResult<Vec<String>> {
        let queues = self
            .peer_man
            .list_device_queues()
            .map_err(conv_peer_man_err)
            .await?;
        let names = queues
            .into_iter()
            .map(|qv| qv.id.to_string())
            .collect::<Vec<_>>();
        Ok(names)
    }

    async fn get_server_notifs(&self) -> RpcResult<Vec<String>> {
        Ok(self
            .peer_man
            .get_server_notifs()
            .map_err(conv_peer_man_err)
            .await?)
    }

    async fn send_raw_d2d_msg(&self, target_device: String, payload_hex: String) -> RpcResult<()> {
        let target = DeviceIdent::from_str(&target_device).map_err(conv_device_err)?;
        let payload = hex::decode(&payload_hex).map_err(conv_hex_err)?;

        // TODO rework this to go through a router to decide if we should give
        // it to them more directly instead of assuming our homeserver can just
        // do it
        info!(%target, len = %payload.len(), "sending raw D2D msg");
        self.peer_man
            .send_raw_d2d_msg(&target, &payload)
            .map_err(conv_peer_man_err)
            .await?;

        Ok(())
    }
}

// TODO make all of these return more data as needed
pub fn conv_hex_err(e: hex::FromHexError) -> ErrorObjectOwned {
    ErrorObjectOwned::owned::<()>(21, format!("{e}"), None)
}

// TODO make all of these return more data as needed
pub fn conv_proto_client_err(e: aspect_proto::core::AspClientError) -> ErrorObjectOwned {
    ErrorObjectOwned::owned::<()>(43, format!("{e}"), None)
}

pub fn conv_peer_man_err(e: aspect_client::peer_manager::Error) -> ErrorObjectOwned {
    ErrorObjectOwned::owned::<()>(43, format!("{e}"), None)
}

pub fn conv_client_err(e: aspect_client::errors::Error) -> ErrorObjectOwned {
    ErrorObjectOwned::owned::<()>(44, format!("{e}"), None)
}
